/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 */

package ledger

import (
	"encoding/binary"

	"github.com/golang/protobuf/proto"
	"hundsun.com/hsl/hschain/common/types"
	"hundsun.com/hsl/hschain/protos"
	pbcom "hundsun.com/hsl/hschain/protos/common"
	protosledger "hundsun.com/hsl/hschain/protos/ledger"
	"hundsun.com/hsl/hschain/store/db"
)

const (
	keyBlockNumber         = "block.num"
	keyBlockSequence       = "block.sequence"
	keyExecutedBlockNumber = "exd.num"
)

// Service ledger struct to kv service
type Service struct {
	db db.Database
}

// NewLedger new ledger
func NewLedger(d db.Database) (*Service, error) {
	if d == nil {
		return nil, types.ErrNilDbInstance
	}
	return &Service{db: d}, nil
}

// GenKey generate key
func GenKey(namespace, key string) []byte {
	return []byte(namespace + key)
}

// KV 对象转kv
func KV(namespace string, p protos.EnhancedProtoMessage) (*pbcom.KeyValue, error) {
	kv := &pbcom.KeyValue{}
	v, err := proto.Marshal(p)
	if err != nil {
		return nil, err
	}
	kv.Value = v
	kv.Key = GenKey(namespace, p.GetXXXKey())
	return kv, nil
}

func (s *Service) set(namespace string, p protos.EnhancedProtoMessage) error {
	if p == nil {
		return nil
	}

	kv, err := KV(namespace, p)
	if err != nil {
		return err
	}

	return s.db.Set(kv.Key, kv.Value)
}

// SaveBlocks 批量保存区块
func (s *Service) bulkSet(namespace string, pms []*protos.EnhancedProtoMessage) error {
	if len(pms) == 0 {
		return nil
	}

	batch := s.db.NewBatch(false)
	for _, pm := range pms {
		if pm == nil {
			// 为nil, 则报错
			return types.ErrContainNilElement
		}

		kv, err := KV(namespace, *pm)
		if err != nil {
			return err
		}
		if err := batch.Set(kv.Key, kv.Value); err != nil {
			return err
		}
	}

	return batch.Write()
}

func (s *Service) convertProtoMsg(p protos.EnhancedProtoMessage) *protos.EnhancedProtoMessage {
	return &p
}

func (s *Service) get(namespace, key string, result protos.EnhancedProtoMessage) error {
	v, err := s.db.Get(GenKey(namespace, key))
	if err != nil {
		return err
	}

	return proto.Unmarshal(v, result)
}

// SetBlockNumber 设置区块号
func (s *Service) SetBlockNumber(num uint64) error {
	kv, _ := BlockNumberKV(num)
	return s.db.Set(kv.Key, kv.Value)
}

// BlockNumberKV kv化
func BlockNumberKV(num uint64) (*pbcom.KeyValue, error) {
	v := make([]byte, 8)
	binary.BigEndian.PutUint64(v, num)
	return &pbcom.KeyValue{Key: GenKey(db.NamespaceLedgerBlock, keyBlockNumber), Value: v}, nil
}

// GetBlockNumber 获取区块号
func (s *Service) GetBlockNumber() (uint64, error) {
	v, err := s.db.Get(GenKey(db.NamespaceLedgerBlock, keyBlockNumber))
	if err != nil {
		return 0, err
	}
	return binary.BigEndian.Uint64(v), nil
}

// SetBlockSequence 设置区块号
func (s *Service) SetBlockSequence(seq uint64) error {
	kv, _ := BlockSequenceKV(seq)
	return s.db.Set(kv.Key, kv.Value)
}

// BlockSequenceKV kv化
func BlockSequenceKV(num uint64) (*pbcom.KeyValue, error) {
	v := make([]byte, 8)
	binary.BigEndian.PutUint64(v, num)
	return &pbcom.KeyValue{Key: GenKey(db.NamespaceLedgerBlock, keyBlockSequence), Value: v}, nil
}

// GetBlockSequence 获取区块号
func (s *Service) GetBlockSequence() (uint64, error) {
	v, err := s.db.Get(GenKey(db.NamespaceLedgerBlock, keyBlockSequence))
	if err != nil {
		return 0, err
	}
	return binary.BigEndian.Uint64(v), nil
}

// SetMaxExecutedBlockNumber 设置最大执行blocknum
func (s *Service) SetMaxExecutedBlockNumber(number uint64) error {
	kv, _ := MaxExecutedBlockNumberKV(number)
	return s.db.Set(kv.Key, kv.Value)
}

// MaxExecutedBlockNumberKV 设置最大执行blocknum
func MaxExecutedBlockNumberKV(number uint64) (*pbcom.KeyValue, error) {
	v := make([]byte, 8)
	binary.BigEndian.PutUint64(v, number)
	return &pbcom.KeyValue{Key: GenKey(db.NamespaceLedgerBlock, keyExecutedBlockNumber), Value: v}, nil
}

// GetMaxExecutedBlockNumber 获取最大执行blocknum
func (s *Service) GetMaxExecutedBlockNumber() (uint64, error) {
	v, err := s.db.Get(GenKey(db.NamespaceLedgerBlock, keyExecutedBlockNumber))
	if err != nil {
		return 0, err
	}
	return binary.BigEndian.Uint64(v), nil
}

// SaveBlock 保存区块
func (s *Service) SaveBlock(block *protosledger.Block) error {
	return s.set(db.NamespaceLedgerBlock, block)
}

// BlockKV block kv化
func BlockKV(block *protosledger.Block) (*pbcom.KeyValue, error) {
	return KV(db.NamespaceLedgerBlock, block)
}

// SaveBlocks 批量保存区块
func (s *Service) SaveBlocks(blocks []*protosledger.Block) error {
	items := make([]*protos.EnhancedProtoMessage, len(blocks))
	for i, block := range blocks {
		items[i] = s.convertProtoMsg(block)
	}
	return s.bulkSet(db.NamespaceLedgerBlock, items)
}

// BlocksKV blocks kv化
func BlocksKV(blocks []*protosledger.Block) (*pbcom.KeyValues, error) {
	kvs := &pbcom.KeyValues{}
	kvs.Kvs = make([]*pbcom.KeyValue, len(blocks))
	for i, block := range blocks {
		kvs.Kvs[i], _ = BlockKV(block)
	}
	return kvs, nil
}

// GetBlock 根据区块编号获取区块
func (s *Service) GetBlock(blockNumber uint64) (*protosledger.Block, error) {
	result := &protosledger.Block{}
	header := &protosledger.BlockHeader{
		Number: blockNumber,
	}
	block := &protosledger.Block{
		Header: header,
	}
	if err := s.get(db.NamespaceLedgerBlock, block.GetXXXKey(), result); err != nil {
		return nil, err
	}
	return result, nil
}

// GetBlocks 获取[startBlockNumber,endBlockNumber]范围内的区块
func (s *Service) GetBlocks(startBlockNumber, endBlockNumber uint64) ([]*protosledger.Block, error) {
	if startBlockNumber > endBlockNumber {
		return nil, types.ErrInvalidParam
	}

	// keys
	keys := make([][]byte, endBlockNumber-startBlockNumber+1)
	header := &protosledger.BlockHeader{
		Number: 0,
	}
	block := &protosledger.Block{
		Header: header,
	}
	for i := startBlockNumber; i <= endBlockNumber; i++ {
		header.Number = i
		keys[i-startBlockNumber] = GenKey(db.NamespaceLedgerBlock, block.GetXXXKey())
	}

	// values
	values, err := s.db.Gets(keys)
	if err != nil {
		return nil, err
	}
	var blocks []*protosledger.Block
	for _, v := range values {
		if v == nil {
			continue
		}

		b := &protosledger.Block{}
		if err := proto.Unmarshal(v, b); err != nil {
			continue
		}
		blocks = append(blocks, b)
	}
	return blocks, nil
}

// SaveBlockExecResult 保存区块执行结果
func (s *Service) SaveBlockExecResult(blockExecResult *protosledger.BlockExecResult) error {
	return s.set(db.NamespaceLedgerBlockExecResult, blockExecResult)
}

// BlockExecResultKV BlockExecResult kv化
func BlockExecResultKV(blockExecResults *protosledger.BlockExecResult) (*pbcom.KeyValue, error) {
	return KV(db.NamespaceLedgerBlockExecResult, blockExecResults)
}

// SaveBlockExecResults 批量保存区块执行结果
func (s *Service) SaveBlockExecResults(blockExecResults []*protosledger.BlockExecResult) error {
	items := make([]*protos.EnhancedProtoMessage, len(blockExecResults))
	for i, result := range blockExecResults {
		items[i] = s.convertProtoMsg(result)
	}
	return s.bulkSet(db.NamespaceLedgerBlockExecResult, items)
}

// BlockExecResultsKV BlockExecResults kv化
func BlockExecResultsKV(blockExecResults []*protosledger.BlockExecResult) (*pbcom.KeyValues, error) {
	kvs := &pbcom.KeyValues{}
	kvs.Kvs = make([]*pbcom.KeyValue, len(blockExecResults))
	for i, block := range blockExecResults {
		kvs.Kvs[i], _ = BlockExecResultKV(block)
	}
	return kvs, nil
}

// GetBlockExecResult 获取区块执行结果
func (s *Service) GetBlockExecResult(blockNumber uint64) (*protosledger.BlockExecResult, error) {
	result := &protosledger.BlockExecResult{}
	blockExeResult := &protosledger.BlockExecResult{
		BlockNumber: blockNumber,
	}
	if err := s.get(db.NamespaceLedgerBlockExecResult, blockExeResult.GetXXXKey(), result); err != nil {
		return nil, err
	}
	return result, nil
}

// GetBlockExecResults 获取[startBlockNumber,endBlockNumber]范围内的区块执行结果
func (s *Service) GetBlockExecResults(startBlockNumber, endBlockNumber uint64) ([]*protosledger.BlockExecResult, error) {
	if startBlockNumber > endBlockNumber {
		return nil, types.ErrInvalidParam
	}

	// keys
	keys := make([][]byte, endBlockNumber-startBlockNumber+1)
	for i := startBlockNumber; i <= endBlockNumber; i++ {
		block := &protosledger.BlockExecResult{
			BlockNumber: i,
		}
		keys[i-startBlockNumber] = GenKey(db.NamespaceLedgerBlockExecResult, block.GetXXXKey())
	}

	// values
	values, err := s.db.Gets(keys)
	if err != nil {
		return nil, err
	}
	var results []*protosledger.BlockExecResult
	for _, v := range values {
		if v == nil {
			continue
		}

		b := &protosledger.BlockExecResult{}
		if err := proto.Unmarshal(v, b); err != nil {
			continue
		}
		results = append(results, b)
	}
	return results, nil
}

// AddTransaction 添加已出块的交易 （该函数不支持缓存）
func (s *Service) AddTransaction(tx *protosledger.ExecutedTransaction) error {
	return s.set(db.NamespaceLedgerExecutedTransaction, tx)
}

// AddTransactionKV AddTransaction kv化
func AddTransactionKV(tx *protosledger.ExecutedTransaction) (*pbcom.KeyValue, error) {
	return KV(db.NamespaceLedgerExecutedTransaction, tx)
}

// AddTransactions 批量添加已出块的交易（该函数不支持缓存）
func (s *Service) AddTransactions(txs []*protosledger.ExecutedTransaction) error {
	items := make([]*protos.EnhancedProtoMessage, len(txs))
	for i, result := range txs {
		items[i] = s.convertProtoMsg(result)
	}
	return s.bulkSet(db.NamespaceLedgerExecutedTransaction, items)
}

// AddTransactionsKV AddTransactions kv化
func AddTransactionsKV(txs []*protosledger.ExecutedTransaction) (*pbcom.KeyValues, error) {
	kvs := &pbcom.KeyValues{}
	kvs.Kvs = make([]*pbcom.KeyValue, len(txs))
	for i, tx := range txs {
		kvs.Kvs[i], _ = AddTransactionKV(tx)
	}
	return kvs, nil
}

// GetTransaction 获取已出块的交易（不支持交易hash重复）
func (s *Service) GetTransaction(txHash protos.Hash) (*protosledger.ExecutedTransaction, error) {
	result := &protosledger.ExecutedTransaction{}
	txReceipt := &protosledger.TransactionReceipt{
		TxHash: txHash,
	}
	execTx := &protosledger.ExecutedTransaction{
		Tx:      nil,
		Receipt: txReceipt,
	}
	if err := s.get(db.NamespaceLedgerExecutedTransaction, execTx.GetXXXKey(), result); err != nil {
		return nil, err
	}
	return result, nil
}

// GetTransactions 批量获取已出块的交易（不支持交易hash重复）
func (s *Service) GetTransactions(txHashes []protos.Hash) ([]*protosledger.ExecutedTransaction, error) {
	if txHashes == nil {
		return nil, nil
	}

	// keys
	keys := make([][]byte, len(txHashes))
	txReceipt := &protosledger.TransactionReceipt{
		TxHash: nil,
	}
	execTx := &protosledger.ExecutedTransaction{
		Tx:      nil,
		Receipt: txReceipt,
	}
	for i, txHash := range txHashes {
		txReceipt.TxHash = txHash
		keys[i] = GenKey(db.NamespaceLedgerExecutedTransaction, execTx.GetXXXKey())
	}

	// values
	values, err := s.db.Gets(keys)
	if err != nil {
		return nil, err
	}
	var execTxs []*protosledger.ExecutedTransaction
	for _, v := range values {
		if v == nil {
			continue
		}

		b := &protosledger.ExecutedTransaction{}
		if err := proto.Unmarshal(v, b); err != nil {
			continue
		}
		execTxs = append(execTxs, b)
	}
	return execTxs, nil
}

// AddTopicInfoKV topic info kv化
func AddTopicInfoKV(topicType string, topicInfo []byte) (*pbcom.KeyValue, error) {
	var kv = &pbcom.KeyValue{}
	kv.Key = GenKey(db.NamespaceLedgerTopicInfo, topicType)
	kv.Value = topicInfo
	return kv, nil
}

// SetCurrentBlockSequence 设置区块号
func (s *Service) SetCurrentBlockSequence(seq uint64) error {
	kv, _ := CurrentBlockSequenceKV(seq)
	return s.db.Set(kv.Key, kv.Value)
}

// CurrentBlockSequenceKV kv化
func CurrentBlockSequenceKV(seq uint64) (*pbcom.KeyValue, error) {
	v := make([]byte, 8)
	binary.BigEndian.PutUint64(v, seq)
	return &pbcom.KeyValue{Key: GenKey(db.NamespaceLedgerSequenceBlock, keyBlockSequence), Value: v}, nil
}

// GetCurrentBlockSequence 获取区块号
func (s *Service) GetCurrentBlockSequence() (uint64, error) {
	v, err := s.db.Get(GenKey(db.NamespaceLedgerSequenceBlock, keyBlockSequence))
	if err != nil {
		if err == types.ErrNotFound {
			return 0, nil
		}
		return 0, err
	}
	return binary.BigEndian.Uint64(v), nil
}
